package fag.core.game.test.playground;

import java.awt.geom.AffineTransform;

import fag.core.collision.Hitbox;
import fag.core.game.AbstractDoodad;
import fag.core.game.Scene;
import fag.core.geometry.Rectangle2D;

public abstract class AbstractPlaygroundDoodad extends AbstractDoodad {
	
	private Hitbox hitbox = null;
	
	private boolean isBoundsCached = false;
	private boolean isHitboxCached = false;
	
	private Rectangle2D boundsCache = null;
	private Hitbox hitboxCache = null;

	/**
	 * Constructeur par défaut.
	 */
	public AbstractPlaygroundDoodad(Hitbox hitbox) {
		
		this.hitbox = hitbox;
	}
	
	@Override
	public Rectangle2D getBounds() {
		
		// Cache hit
		if(isBoundsCached)
			return boundsCache.clone();
		
		// Création du cache
		synchronized(transformation) {
			boundsCache = hitbox.getBounds().transform(transformation).getBounds();
		}
			
		isBoundsCached = true;
		
		return boundsCache.clone();
	}

	@Override
	public Hitbox getHitbox() {

		// Cache hitbox
		if(isHitboxCached)
			return hitboxCache.clone();
		
		// Création du cache
		synchronized(transformation) {
			hitboxCache = hitbox.transform(transformation);
		}
		
		isHitboxCached = true;
		
		return hitboxCache.clone();
	}

	@Override
	public void rotate(double angle, double x, double y, Referential referential) {

		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null; 

		synchronized(transformation) {
			
			oldBounds = hitbox.getBounds().transform(transformation).getBounds();
		
			AffineTransform rotation = AffineTransform.getRotateInstance(angle, x, y);
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(rotation);
				break;
				
			case RELATIVE:
				transformation.concatenate(rotation);
				break;
			}
			
			newBounds = hitbox.getBounds().transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}

	@Override
	public void scale(double sx, double sy, double ax, double ay, Referential referential) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(transformation) {
			
			oldBounds = hitbox.getBounds().transform(transformation).getBounds();
			
			AffineTransform scaling = AffineTransform.getScaleInstance(sx, sy);
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(AffineTransform.getTranslateInstance(-ax, -ay));
				transformation.preConcatenate(scaling);
				transformation.preConcatenate(AffineTransform.getTranslateInstance(ax, ay));
				break;
				
			case RELATIVE:
				transformation.concatenate(AffineTransform.getTranslateInstance(-ax, -ay));
				transformation.concatenate(scaling);
				transformation.concatenate(AffineTransform.getTranslateInstance(ax, ay));
				break;
			}
			
			newBounds = hitbox.getBounds().transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}	
	}

	@Override
	public void setRotation(double angle) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(transformation) {
			
			double[] flatmatrix = new double[6];
			transformation.getMatrix(flatmatrix);
			
			double cos = Math.cos(angle);
			double sin = Math.sin(angle);
			
			oldBounds = hitbox.getBounds().transform(transformation).getBounds();
			transformation.setTransform(cos, sin, -sin, cos, flatmatrix[4], flatmatrix[5]);
			newBounds = hitbox.getBounds().transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}

	@Override
	public void setTransformation(AffineTransform transformation) {
		
		isBoundsCached = false;
		isHitboxCached = false;

		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(transformation) {
		
			oldBounds = hitbox.getBounds().transform(transformation).getBounds();
			this.transformation = transformation;
			newBounds = hitbox.getBounds().transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}

	@Override
	public void setTranslation(double x, double y) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(transformation) {
		
			double[] flatmatrix = new double[6];
			transformation.getMatrix(flatmatrix);
			
			oldBounds = hitbox.getBounds().transform(transformation).getBounds();
			transformation.setTransform(flatmatrix[0], flatmatrix[1], flatmatrix[2], flatmatrix[3], x, y);
			newBounds = hitbox.getBounds().transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}

	@Override
	public void translate(double x, double y, Referential referential) {

		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(transformation) {
			
			oldBounds = hitbox.getBounds().transform(transformation).getBounds();
			
			AffineTransform translation = AffineTransform.getTranslateInstance(x, y);
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(translation);
				break;
				
			case RELATIVE:
				transformation.concatenate(translation);
				break;
			}
			
			newBounds = hitbox.getBounds().transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}
	
	@Override
	protected void forward(double dt) {
	}
	
	@Override
	protected void setScene(Scene scene) {
		
		// Ajout
		if(scene != null) {
			
			this.scene = scene;
			
			synchronized(transformation) {
				scene.addDirtyRegion(getBounds());
			}
		}
		// Suppression
		else {
			
			Scene tmp = this.scene;
			this.scene = null;
			
			synchronized(transformation) {
				tmp.addDirtyRegion(getBounds());
			}
		}
	}
}
